using HybridCLR;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using YooAsset;


/// <summary>
/// 脚本工作流程：
/// 1.下载资源，用yooAsset资源框架进行下载
///    1.资源文件，ab包
///    2.热更新dll
    /// 2.给AOT DLL补充元素据，通过RuntimeApi.LoadMetadataForAOTAssembly
    /// 3.通过实例化prefab，运行热更代码
    /// </summary>
namespace MyGame
{
    public class LoadDll : MonoBehaviour
    {
        /// <summary>
        /// 资源系统运行模式
        /// </summary>
        public EPlayMode PlayMode = EPlayMode.HostPlayMode;

        private ResourcePackage _defaultPackage;
        private string[] uiList;
        private Transform uiTransform;

        private void Awake()
        {
           
        }

        void Start()
        {
            StartCoroutine(InitYooAssets(StartGame));
        }

        #region YooAsset初始化

        IEnumerator InitYooAssets(Action onDownloadComplete)
        {
            // 1.初始化资源系统
            YooAssets.Initialize();

            string packageName = "DefaultPackage";
            var package = YooAssets.TryGetPackage(packageName) ?? YooAssets.CreatePackage(packageName);
            YooAssets.SetDefaultPackage(package);
            if (PlayMode == EPlayMode.EditorSimulateMode)
            {
                //编辑器模拟模式
                var initParameters = new EditorSimulateModeParameters { SimulateManifestFilePath = EditorSimulateModeHelper.SimulateBuild(EDefaultBuildPipeline.BuiltinBuildPipeline, "DefaultPackage") };
                yield return package.InitializeAsync(initParameters);
            }
            else if (PlayMode == EPlayMode.HostPlayMode)
            {
                //联机运行模式
                string defaultHostServer = GetHostServerURL();
                string fallbackHostServer = GetHostServerURL();
                Debug.Log(defaultHostServer);
                var initParameters = new HostPlayModeParameters();
                initParameters.BuildinQueryServices = new GameQueryServices();

                initParameters.RemoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
                var initOperation = package.InitializeAsync(initParameters);
                yield return initOperation;

                if (initOperation.Status == EOperationStatus.Succeed)
                {
                    Debug.Log("资源包初始化成功！");
                }
                else
                {
                    Debug.LogError($"资源包初始化失败：{initOperation.Error}");
                }
            }


            //2.获取资源版本
            var operation = package.UpdatePackageVersionAsync();
            yield return operation;

            if (operation.Status != EOperationStatus.Succeed)
            {
                //更新失败
                Debug.LogError(operation.Error);
                yield break;
            }

            string packageVersion = operation.PackageVersion;
            Debug.Log($"Updated package Version : {packageVersion}");

            //3.更新补丁清单
            // 更新成功后自动保存版本号，作为下次初始化的版本。
            // 也可以通过operation.SavePackageVersion()方法保存。
            var operation2 = package.UpdatePackageManifestAsync(packageVersion);
            yield return operation2;

            if (operation2.Status != EOperationStatus.Succeed)
            {
                //更新失败
                Debug.LogError(operation2.Error);
                yield break;
            }

            //4.下载补丁包
            yield return Download();

            //判断是否下载成功
            var assets = new List<string> { "HotUpdate.dll" }.Concat(AOTMetaAssemblyFiles);
            foreach (var asset in assets)
            {
                var handle = package.LoadAssetAsync<TextAsset>(asset);
                yield return handle;
                var assetObj = handle.AssetObject as TextAsset;
                s_assetDatas[asset] = assetObj;
                Debug.Log($"dll:{asset}   {assetObj == null}");
            }

            _defaultPackage = package;
            onDownloadComplete();
        }

        private string GetHostServerURL()
        {
            //模拟下载地址，8088为Nginx里面设置的端口号，项目名，平台名
            //http://127.0.0.1:80/TestProject/PC
            return "http://127.0.0.1:80/GameDownAssets/PC";
        }

        /// <summary>
        /// 远端资源地址查询服务类
        /// </summary>
        private class RemoteServices : IRemoteServices
        {
            private readonly string _defaultHostServer;
            private readonly string _fallbackHostServer;

            public RemoteServices(string defaultHostServer, string fallbackHostServer)
            {
                _defaultHostServer = defaultHostServer;
                _fallbackHostServer = fallbackHostServer;
            }

            string IRemoteServices.GetRemoteMainURL(string fileName)
            {
                return $"{_defaultHostServer}/{fileName}";
            }

            string IRemoteServices.GetRemoteFallbackURL(string fileName)
            {
                return $"{_fallbackHostServer}/{fileName}";
            }
        }

        /// <summary>
        /// 资源文件查询服务类
        /// </summary>
        internal class GameQueryServices : IBuildinQueryServices
        {
            public bool Query(string packageName, string fileName, string fileCRC)
            {
#if UNITY_IPHONE
            throw new Exception("Ios平台需要内置资源");
            return false;
#else
                return false;
#endif
            }
        }

        #endregion

        #region 下载热更资源

        IEnumerator Download()
        {
            int downloadingMaxNum = 10;
            int failedTryAgain = 3;
            var package = YooAssets.GetPackage("DefaultPackage");
            var downloader = package.CreateResourceDownloader(downloadingMaxNum, failedTryAgain);

            //没有需要下载的资源
            if (downloader.TotalDownloadCount == 0)
            {
                yield break;
            }

            //需要下载的文件总数和总大小
            int totalDownloadCount = downloader.TotalDownloadCount;
            long totalDownloadBytes = downloader.TotalDownloadBytes;

            //注册回调方法
            downloader.OnDownloadErrorCallback = OnDownloadErrorFunction;
            downloader.OnDownloadProgressCallback = OnDownloadProgressUpdateFunction;
            downloader.OnDownloadOverCallback = OnDownloadOverFunction;
            downloader.OnStartDownloadFileCallback = OnStartDownloadFileFunction;

            //开启下载
            downloader.BeginDownload();
            yield return downloader;

            //检测下载结果
            if (downloader.Status == EOperationStatus.Succeed)
            {
                //下载成功
                Debug.Log("更新完成");
            }
            else
            {
                //下载失败
                Debug.Log("更新失败");
            }
        }

        /// <summary>
        /// 开始下载
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="sizeBytes"></param>
        private void OnStartDownloadFileFunction(string fileName, long sizeBytes)
        {
            Debug.Log(string.Format("开始下载：文件名：{0}，文件大小：{1}", fileName, sizeBytes));
        }

        /// <summary>
        /// 下载完成
        /// </summary>
        /// <param name="isSucceed"></param>
        private void OnDownloadOverFunction(bool isSucceed)
        {
            Debug.Log("下载" + (isSucceed ? "成功" : "失败"));
        }

        /// <summary>
        /// 更新中
        /// </summary>
        /// <param name="totalDownloadCount"></param>
        /// <param name="currentDownloadCount"></param>
        /// <param name="totalDownloadBytes"></param>
        /// <param name="currentDownloadBytes"></param>
        private void OnDownloadProgressUpdateFunction(int totalDownloadCount, int currentDownloadCount, long totalDownloadBytes, long currentDownloadBytes)
        {
            Debug.Log(string.Format("文件总数：{0}，已下载文件数：{1}，下载总大小：{2}，已下载大小{3}", totalDownloadCount, currentDownloadCount, totalDownloadBytes, currentDownloadBytes));
        }

        /// <summary>
        /// 下载出错
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="error"></param>
        private void OnDownloadErrorFunction(string fileName, string error)
        {
            Debug.Log(string.Format("下载出错：文件名：{0}，错误信息：{1}", fileName, error));
        }

        #endregion

        #region 补充元数据

        //补充元数据dll的列表
        //通过RuntimeApi.LoadMetadataForAOTAssembly()函数来补充AOT泛型的原始元数据
        private static List<string> AOTMetaAssemblyFiles { get; } = new() { "mscorlib.dll", "System.dll", "System.Core.dll", };
        private static Dictionary<string, TextAsset> s_assetDatas = new Dictionary<string, TextAsset>();
        private static Assembly _hotUpdateAss;

        public static byte[] ReadBytesFromStreamingAssets(string dllName)
        {
            if (s_assetDatas.ContainsKey(dllName))
            {
                return s_assetDatas[dllName].bytes;
            }

            return Array.Empty<byte>();
        }



        /// <summary>
        /// 为aot assembly加载原始metadata， 这个代码放aot或者热更新都行。
        /// 一旦加载后，如果AOT泛型函数对应native实现不存在，则自动替换为解释模式执行
        /// </summary>
        private static void LoadMetadataForAOTAssemblies()
        {
            /// 注意，补充元数据是给AOT dll补充元数据，而不是给热更新dll补充元数据。
            /// 热更新dll不缺元数据，不需要补充，如果调用LoadMetadataForAOTAssembly会返回错误
            HomologousImageMode mode = HomologousImageMode.SuperSet;
            foreach (var aotDllName in AOTMetaAssemblyFiles)
            {
                byte[] dllBytes = ReadBytesFromStreamingAssets(aotDllName);
                // 加载assembly对应的dll，会自动为它hook。一旦aot泛型函数的native函数不存在，用解释器版本代码
                LoadImageErrorCode err = RuntimeApi.LoadMetadataForAOTAssembly(dllBytes, mode);
                Debug.Log($"LoadMetadataForAOTAssembly:{aotDllName}. mode:{mode} ret:{err}");
            }
        }

        #endregion

        #region 运行测试

        void StartGame()
        {
            // 加载AOT dll的元数据
            LoadMetadataForAOTAssemblies();
            // 加载热更dll
#if !UNITY_EDITOR
        _hotUpdateAss = Assembly.Load(ReadBytesFromStreamingAssets("HotUpdate.dll"));
#else
            _hotUpdateAss = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "HotUpdate");
#endif
            Debug.Log("运行热更代码");
            
            //先加载Manager类
            StartCoroutine(Run_InstantiateComponentGameManager());

            //再读取UI文件配表
            StartCoroutine(Run_InstantiateComponentTextAssets());

            //再根据读取到的配表数据加载UI
            StartCoroutine(Run_InstantiateComponentUIManager());
        }

        IEnumerator Run_InstantiateComponentGameManager()
        {
            // 通过实例化assetbundle中的资源，还原资源上的热更新脚本
            var package = YooAssets.GetPackage("DefaultPackage");
            var handle = package.LoadAssetAsync<GameObject>("GameManager");
            yield return handle;
            handle.Completed += Handle_CompletedGameManager;
        }

        private void Handle_CompletedGameManager(AssetHandle obj)
        {
            Debug.Log("准备实例化");
            GameObject go = obj.InstantiateSync();
            Debug.Log($"Prefab name is {go.name}");
        }

        IEnumerator Run_InstantiateComponentTextAssets()
        {
            // 通过实例化assetbundle中的资源，还原资源上的热更新脚本
            var package = YooAssets.GetPackage("DefaultPackage");
            AssetHandle handle = package.LoadAssetSync<TextAsset>("UIList");
            yield return handle;
            handle.Completed += Handle_CompletedTextAssets;
        }

        private void Handle_CompletedTextAssets(AssetHandle obj)
        {
            TextAsset ui_text = obj.AssetObject as TextAsset;
            string str = ui_text.text;
            uiList = str.Split(new[] { '\r', '\n' },StringSplitOptions.RemoveEmptyEntries);       
        }

        IEnumerator Run_InstantiateComponentUIManager()
        {
            // 通过实例化assetbundle中的资源，还原资源上的热更新脚本
            var package = YooAssets.GetPackage("DefaultPackage");
            var handle = package.LoadAssetAsync<GameObject>("UIManager");
            yield return handle;
            handle.Completed += Handle_CompletedUIManager;
        }

        private void Handle_CompletedUIManager(AssetHandle obj)
        {
            Debug.Log("准备实例化");
            GameObject go = obj.InstantiateSync();
            Debug.Log($"Prefab name is {go.name}");

            uiTransform = go.transform;

            foreach (var item in uiList)
            {
                StartCoroutine(Run_InstantiateComponentChildUI(item));
            }
        }

        IEnumerator Run_InstantiateComponentChildUI(string ui_name)
        {
            // 通过实例化assetbundle中的资源，还原资源上的热更新脚本
            var package = _defaultPackage;
            var handle = package.LoadAssetAsync<GameObject>(ui_name);
            yield return handle;
            handle.Completed += Handle_CompletedChildUI;
        }

        private void Handle_CompletedChildUI(AssetHandle obj)
        {
            Debug.Log("准备实例化");
            GameObject go = obj.InstantiateSync(uiTransform);
            Debug.Log($"Prefab name is {go.name}");
        }

        #endregion
    }
    //PS:版本不同可能有一些类名发生变化，请参照现阶段版本自行修改，官网可能更新不及时。
}
